package com.service.single.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.service.single.constant.FileType;
import com.service.single.constant.RedisKey;
import com.service.single.entity.FileInfo;
import com.service.single.exception.BizException;
import com.service.single.mapper.FileMapper;
import com.service.single.service.ElasticsearchService;
import com.service.single.service.FileService;
import com.service.single.util.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: 一休
 * @DateTime: 2022/11/2 14:08
 */
@Slf4j
@Service
public class FileServiceImpl extends ServiceImpl<FileMapper, FileInfo> implements FileService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ElasticsearchService elasticsearchService;

    @Value("${file.path:/opt/data/fileDir/importData/}")
    private String path;

    @Override
    public void uploadFile(MultipartFile file) {
        String time = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String originalFilename = file.getOriginalFilename();
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        Long id = IdUtil.getSnowflakeNextId();
        StringBuilder filePath = new StringBuilder();
        filePath.append(path).append(time).append("/");
        String path = filePath.toString();
        File f = new File(path);
        if (!f.exists()) {
            f.mkdirs();
        }
        filePath.append(id).append(suffix);
        FileInfo fileInfo = new FileInfo();
        fileInfo.setFileName(originalFilename);
        fileInfo.setFilePath(filePath.toString());
        fileInfo.setId(id);
        Integer fileType = FileType.fileType().get(suffix.replace(".", ""));
        if (null == fileType) {
            throw new BizException(HttpStatus.BAD_REQUEST.value(), "不支持的文件类型");
        }
        fileInfo.setFileType(fileType);
        String loginName = TokenUtil.parseJwt().get("loginName").asString();
        fileInfo.setOperator(loginName);
        try {
            file.transferTo(new File(filePath.toString()));
            redisTemplate.opsForList().leftPush(RedisKey.FILE, JSON.toJSONString(fileInfo));
            this.save(fileInfo);
        } catch (Exception e) {
            log.error("文件上传异常 {}", e.getMessage());
            throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "文件上传失败");
        }
    }

    @Override
    public void uploadBatchFiles(MultipartFile[] files) {
        String time = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        StringBuilder builder = new StringBuilder();
        builder.append(path).append(time).append("/");
        String path = builder.toString();
        File f = new File(path);
        if (!f.exists()) {
            f.mkdirs();
        }
        for (MultipartFile file : files) {
            String originalFilename = file.getOriginalFilename();
            String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
            Integer fileType = FileType.fileType().get(suffix.replace(".", ""));
            if (null == fileType) {
                throw new BizException(HttpStatus.BAD_REQUEST.value(), "不支持的文件类型");
            }
            Long id = IdUtil.getSnowflakeNextId();
            String filePath = builder.toString() + id + suffix;
            FileInfo fileInfo = new FileInfo();
            fileInfo.setFileName(originalFilename);
            fileInfo.setFilePath(filePath);
            fileInfo.setId(id);
            fileInfo.setFileType(fileType);
            String loginName = TokenUtil.parseJwt().get("loginName").asString();
            fileInfo.setOperator(loginName);
            try {
                file.transferTo(new File(filePath));
                redisTemplate.opsForList().leftPush(RedisKey.FILE, JSON.toJSONString(fileInfo));
                this.save(fileInfo);
            } catch (Exception e) {
                log.error("文件上传异常 {}", e.getMessage());
                throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "文件上传失败");
            }
        }
    }

    @Override
    public void downloadFile(Long id) {
        FileInfo fileInfo = this.getById(id);
        if (null == fileInfo) {
            throw new BizException(HttpStatus.BAD_REQUEST.value(), "文件不存在");
        }
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        try {
            String filePath = fileInfo.getFilePath();
            String fileName = fileInfo.getFileName();
            // 创建输出流对象
            ServletOutputStream outputStream = response.getOutputStream();
            //以字节数组的形式读取文件
            byte[] bytes = FileUtil.readBytes(filePath);
            // 设置返回内容格式
            response.setContentType("application/octet-stream");
            // 把文件名按UTF-8取出并按ISO8859-1编码，保证弹出窗口中的文件名中文不乱码
            // 中文不要太多，最多支持17个中文，因为header有150个字节限制。
            // 这一步一定要在读取文件之后进行，否则文件名会乱码，找不到文件
            fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
            // 设置下载弹窗的文件名和格式（文件名要包括名字和文件格式）
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            // 返回数据到输出流对象中
            outputStream.write(bytes);
            // 关闭流对象
            IoUtil.close(outputStream);
        } catch (Exception e) {
            log.error("文件下载失败 {}", e.getMessage());
            throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "文件下载失败");
        }
    }

    @Override
    public void deleteFile(Long id) {
        FileInfo fileInfo = this.getById(id);
        if (null == fileInfo) {
            throw new BizException(HttpStatus.BAD_REQUEST.value(), "文件不存在");
        }
        String filePath = fileInfo.getFilePath();
        File file = new File(filePath);
        if (file.exists()) {
            file.delete();
        }
        this.removeById(id);
        // 删除es数据
        elasticsearchService.deleteFileContent(id);
    }

    private static final String TEMP_DIR = System.getProperty("user.home") + File.separator + "Downloads/";

    @Override
    public Map<String, Object> uploadChunk(MultipartFile file, int chunkNumber, int totalChunks, String identifier) {
        Map<String, Object> result = new HashMap<>();
        try {
            // 创建临时存储目录
            File tempDir = new File(TEMP_DIR + identifier);
            if (!tempDir.exists()) {
                tempDir.mkdirs();
            }
            // 保存分片文件
            File chunkFile = new File(tempDir, "chunk_" + chunkNumber);
            try (InputStream inputStream = file.getInputStream();
                 FileOutputStream outputStream = new FileOutputStream(chunkFile)) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
            result.put("success", true);
            result.put("message", "分片上传成功");
        } catch (IOException e) {
            result.put("success", false);
            result.put("message", "分片上传失败: " + e.getMessage());
        }
        return result;
    }

    @Override
    public Map<String, Object> mergeChunks(String identifier, int totalChunks) {
        Map<String, Object> result = new HashMap<>();
        try {
            File tempDir = new File(TEMP_DIR + identifier);
            if (tempDir.exists() && tempDir.isDirectory()) {
                File mergedFile = new File(TEMP_DIR + identifier.split("_")[0]);
                try (FileOutputStream outputStream = new FileOutputStream(mergedFile)) {
                    for (int i = 0; i < totalChunks; i++) {
                        File chunkFile = new File(tempDir, "chunk_" + i);
                        if (chunkFile.exists()) {
                            try (InputStream inputStream = java.nio.file.Files.newInputStream(chunkFile.toPath())) {
                                byte[] buffer = new byte[4096];
                                int bytesRead;
                                while ((bytesRead = inputStream.read(buffer)) != -1) {
                                    outputStream.write(buffer, 0, bytesRead);
                                }
                            }
                            // 删除分片文件
                            chunkFile.delete();
                        }
                    }
                }
                // 删除临时目录
                tempDir.delete();
                result.put("success", true);
                result.put("message", "文件合并成功");
            } else {
                result.put("success", false);
                result.put("message", "临时目录不存在");
            }
        } catch (IOException e) {
            result.put("success", false);
            result.put("message", "文件合并失败: " + e.getMessage());
        }
        return result;
    }
}
